// lib/network/api_client.dart
// ignore_for_file: prefer_const_constructors

import 'dart:async';
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart' hide Response;
import 'package:get_storage/get_storage.dart';
import 'package:fluttertoast/fluttertoast.dart';

/// 业务异常（code != 0）
class ApiException implements Exception {
  final int httpCode;
  final int? bizCode;
  final String message;
  ApiException({this.httpCode = -1, this.bizCode, required this.message});
  @override
  String toString() =>
      'ApiException(http=$httpCode, code=$bizCode, msg=$message)';
}

class HttpClient {
  static final HttpClient _instance = HttpClient._internal();
  factory HttpClient() => _instance;

  late final Dio dio;
  late final Dio _refreshDio; // 专用刷新客户端（不走拦截器，避免递归）
  final _box = GetStorage();

  bool _loginDialogShowing = false;

  Future<void> _clearAuth() async {
    _box.remove('token');
    _box.remove('refreshToken');
  }

  Future<void> _showLoginConfirmDialog() async {
    if (_loginDialogShowing) return;
    _loginDialogShowing = true;

    await Get.defaultDialog(
      title: '登录已过期',
      middleText: '需要重新登录以继续操作',
      textCancel: '取消',
      textConfirm: '去登录',
      barrierDismissible: false,
      onCancel: () {
        _loginDialogShowing = false;
      },
      onConfirm: () {
        _loginDialogShowing = false;
        Get.back(); // 先关弹窗
        Get.offAllNamed('/login'); // 跳转到登录页（清栈）
      },
    );
  }

  // === 你项目里的接口路径（用于判断哪些请求不参与刷新/重试） ===
  static const String loginPath = '/auth/login';
  static const String refreshPath = '/auth/refresh';

  // === 刷新队列与状态 ===
  bool _isRefreshing = false;
  final List<_QueuedRequest> _queue = [];

  HttpClient._internal() {
    const baseUrl = String.fromEnvironment(
      'API_BASE',
      defaultValue: 'http://localhost:8001',
    );
    dio = Dio(
      BaseOptions(
        baseUrl: baseUrl,
        connectTimeout: const Duration(seconds: 15),
        receiveTimeout: const Duration(seconds: 15),
        // headers: {
        //   'Accept': 'application/json',
        //   'Content-Type': 'application/json; charset=utf-8',
        // },
        responseType: ResponseType.json,
      ),
    );

    _refreshDio = Dio(
      BaseOptions(baseUrl: baseUrl, responseType: ResponseType.json),
    );

    // 可选日志
    dio.interceptors.add(
      LogInterceptor(
        requestHeader: false,
        requestBody: true,
        responseBody: true,
        responseHeader: false,
        error: true,
      ),
    );
    dio.interceptors.clear(); // 移除所有拦截器
    // 统一拦截
    dio.interceptors.add(
      InterceptorsWrapper(
        onRequest: (options, handler) async {
          final token = _box.read<String>('token');
          if (token != null && token.isNotEmpty) {
            options.headers['Authorization'] = 'Bearer $token';
          }
          return handler.next(options);
        },
        onResponse: (response, handler) async {
          final data = response.data;
          if (data is Map && data.containsKey('code')) {
            final code = data['code'] as int? ?? -1;
            final msg = (data['message'] ?? '请求失败').toString();
            debugPrint('API Response: $data');
            debugPrint('API Response code: $code');
            if (code == 401) {
              await _clearAuth();
              // 判断当前页面是否是登录页
              if (Get.currentRoute != '/login') {
                await _showLoginConfirmDialog();
              }
              return handler.reject(
                DioException(
                  requestOptions: response.requestOptions,
                  response: response,
                  type: DioExceptionType.badResponse,
                  error: '未登录或登录已过期',
                ),
              );
            }

            if (code != 0) {
              // 居中弹窗
              if (Get.isDialogOpen != true) {
                Fluttertoast.showToast(
                  msg: msg, // 你的错误文案
                  gravity: ToastGravity.CENTER, // 居中
                  toastLength: Toast.LENGTH_SHORT, // 或 Toast.LENGTH_LONG
                  backgroundColor: Colors.black87,
                  textColor: Colors.white,
                  fontSize: 14,
                );
              }
              return handler.reject(
                DioException(
                  requestOptions: response.requestOptions,
                  response: response,
                  type: DioExceptionType.badResponse,
                  error: msg,
                ),
              );
            }

            // 透传 data，调用方直接拿到 data
            response.data = data['data'];
          }
          return handler.next(response);
        },
        onError: (e, handler) async {
          // 非 401 或者是登录/刷新接口本身，直接包装后抛出
          final status = e.response?.statusCode ?? -1;
          final path = e.requestOptions.path;
          final isAuthApi = path == loginPath || path == refreshPath;
          if (status != 401 || isAuthApi) {
            return handler.reject(_wrapFriendlyError(e));
          }

          // 401 且不是登录/刷新：进入刷新队列
          final completer = Completer<Response>();
          _queue.add(_QueuedRequest(e.requestOptions, completer));

          if (!_isRefreshing) {
            _isRefreshing = true;
            try {
              final ok = await _refreshToken(); // 刷新一次
              _isRefreshing = false;

              if (ok) {
                // 刷新成功 → 逐个重放队列
                final tasks = _queue.map((q) => _retry(q.options)).toList();
                final results = await Future.wait(tasks);
                // 逐个完成队列的 completer
                for (int i = 0; i < _queue.length; i++) {
                  _queue[i].completer.complete(results[i]);
                }
                _queue.clear();
              } else {
                // 刷新失败 → 清空队列并抛错，必要时清理本地并跳登录
                final err = _makeAuthFailedError(e);
                for (final q in _queue) {
                  q.completer.completeError(err);
                }
                _queue.clear();
              }
            } catch (err) {
              _isRefreshing = false;
              final dioErr = err is DioException ? err : _wrapFriendlyError(e);
              for (final q in _queue) {
                q.completer.completeError(dioErr);
              }
              _queue.clear();
            }
          }

          // 当前这个 401 请求等待 retry 结果
          try {
            final resp = await completer.future;
            return handler.resolve(resp);
          } catch (err) {
            return handler.reject(
              err is DioException ? err : _wrapFriendlyError(e),
            );
          }
        },
      ),
    );
  }

  // —— 刷新 Token（示例：POST /auth/refresh {refreshToken}） ——
  Future<bool> _refreshToken() async {
    final refreshToken = _box.read<String>('refreshToken');
    if (refreshToken == null || refreshToken.isEmpty) return false;

    try {
      final resp = await _refreshDio.post(
        refreshPath,
        data: {'refreshToken': refreshToken},
      );
      // 刷新接口也可能是 {code,msg,data}，做个解包容错
      dynamic raw = resp.data;
      if (raw is String && raw.isNotEmpty) raw = jsonDecode(raw);

      if (raw is Map && raw.containsKey('code')) {
        if (raw['code'] == 0) {
          final data = raw['data'] ?? {};
          final newToken = (data['token'] ?? '') as String;
          final newRefresh =
              (data['refreshToken'] ?? '') as String? ?? refreshToken;
          if (newToken.isNotEmpty) _box.write('token', newToken);
          _box.write('refreshToken', newRefresh);
          return true;
        } else {
          return false;
        }
      }

      // 非统一格式，尝试直取
      final newToken = (raw['token'] ?? '') as String? ?? '';
      if (newToken.isNotEmpty) {
        _box.write('token', newToken);
        final newRefresh = (raw['refreshToken'] ?? refreshToken) as String;
        _box.write('refreshToken', newRefresh);
        return true;
      }
      return false;
    } catch (_) {
      return false;
    }
  }

  // —— 重放原请求 ——
  Future<Response> _retry(RequestOptions req) async {
    final token = _box.read<String>('token');
    final headers = Map<String, dynamic>.from(req.headers);
    if (token != null && token.isNotEmpty) {
      headers['Authorization'] = 'Bearer $token';
    }
    final options = Options(
      method: req.method,
      headers: headers,
      responseType: req.responseType,
      contentType: req.contentType,
      followRedirects: req.followRedirects,
      validateStatus: req.validateStatus,
      receiveDataWhenStatusError: req.receiveDataWhenStatusError,
      extra: Map<String, dynamic>.from(req.extra)..['__retried'] = true,
    );
    return dio.request(
      req.path,
      data: req.data,
      queryParameters: req.queryParameters,
      options: options,
    );
  }

  DioException _makeAuthFailedError(DioException original) {
    // 可在此清理 token 并跳登录
    _box.remove('token');
    _box.remove('refreshToken');
    _box.remove('userInfo');
    // Get.offAllNamed('/login'); // 需要的话打开

    return DioException(
      requestOptions: original.requestOptions,
      response: original.response,
      type: DioExceptionType.badResponse,
      error: '登录已过期，请重新登录',
    );
  }

  // —— 统一友好错误包装（网络/超时/HTTP 错误） ——
  DioException _wrapFriendlyError(DioException e) {
    String msg;
    if (e.type == DioExceptionType.connectionTimeout ||
        e.type == DioExceptionType.receiveTimeout ||
        e.type == DioExceptionType.sendTimeout) {
      msg = '请求超时，请稍后重试';
    } else if (e.type == DioExceptionType.badResponse) {
      final code = e.response?.statusCode ?? -1;
      msg = '服务异常（$code）';
    } else if (e.type == DioExceptionType.unknown) {
      msg = '网络异常或服务器不可达';
    } else {
      msg = e.message ?? '请求失败';
    }
    return DioException(
      requestOptions: e.requestOptions,
      response: e.response,
      type: e.type,
      error: msg,
    );
  }

  // —— 解包统一返回：{code,msg,data}，code==0→返回 data，其他→吐司并抛异常 ——
  // api_client.dart 中的 _unwrap
  T _unwrap<T>(
    Response res, {
    T Function(dynamic data)? convert,
    bool toastOnBizError = true,
  }) {
    final httpCode = res.statusCode ?? -1;
    dynamic raw = res.data;
    // ... 省略 code/msg 判断，拿到 data 变量 ...
    final data = raw is Map && raw.containsKey('code') ? raw['data'] : raw;

    if (convert != null) return convert(data);

    // —— 简单类型保护，避免 _JsonMap 强转 List 报错 —— //
    final want = T.toString();
    if (want.startsWith('List<')) {
      if (data is List) return data as T;
      if (data is Map) {
        final list =
            data['list'] ?? data['items'] ?? data['records'] ?? data['rows'];
        if (list is List) return list as T;
      }
      throw ApiException(
        httpCode: httpCode,
        message: '期望 $want 但实际是 ${data.runtimeType}（请在调用处提供 convert 解析列表字段）',
      );
    }
    if (want.startsWith('Map<')) {
      if (data is Map) return data as T;
      throw ApiException(
        httpCode: httpCode,
        message: '期望 $want 但实际是 ${data.runtimeType}',
      );
    }

    // 其他类型按原样返回/尝试转换
    return data as T; // 保持原有行为
  }

  // —— RESTful 方法（自动 401 刷新 & 自动解包） ——
  Future<T> getJson<T>(
    String path, {
    Map<String, dynamic>? query,
    CancelToken? cancelToken,
    T Function(dynamic data)? convert,
    bool toastOnBizError = true,
  }) async {
    final res = await dio.get(
      path,
      queryParameters: query,
      cancelToken: cancelToken,
    );
    return _unwrap<T>(res, convert: convert, toastOnBizError: toastOnBizError);
  }

  Future<T> postJson<T>(
    String path,
    dynamic body, {
    Map<String, dynamic>? query,
    CancelToken? cancelToken,
    T Function(dynamic data)? convert,
    bool toastOnBizError = true,
  }) async {
    final res = await dio.post(
      path,
      data: body,
      queryParameters: query,
      cancelToken: cancelToken,
    );
    return _unwrap<T>(res, convert: convert, toastOnBizError: toastOnBizError);
  }

  Future<T> putJson<T>(
    String path,
    dynamic body, {
    Map<String, dynamic>? query,
    CancelToken? cancelToken,
    T Function(dynamic data)? convert,
    bool toastOnBizError = true,
  }) async {
    final res = await dio.put(
      path,
      data: body,
      queryParameters: query,
      cancelToken: cancelToken,
    );
    return _unwrap<T>(res, convert: convert, toastOnBizError: toastOnBizError);
  }

  Future<T> patchJson<T>(
    String path,
    dynamic body, {
    Map<String, dynamic>? query,
    CancelToken? cancelToken,
    T Function(dynamic data)? convert,
    bool toastOnBizError = true,
  }) async {
    final res = await dio.patch(
      path,
      data: body,
      queryParameters: query,
      cancelToken: cancelToken,
    );
    return _unwrap<T>(res, convert: convert, toastOnBizError: toastOnBizError);
  }

  Future<T> deleteJson<T>(
    String path, {
    dynamic body,
    Map<String, dynamic>? query,
    CancelToken? cancelToken,
    T Function(dynamic data)? convert,
    bool toastOnBizError = true,
  }) async {
    final res = await dio.delete(
      path,
      data: body,
      queryParameters: query,
      cancelToken: cancelToken,
    );
    return _unwrap<T>(res, convert: convert, toastOnBizError: toastOnBizError);
  }
}

class _QueuedRequest {
  final RequestOptions options;
  final Completer<Response> completer;
  _QueuedRequest(this.options, this.completer);
}
